package net.mujiwulian.configs.security;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import net.mujiwulian.service.UserSimpleService;
import net.mujiwulian.utils.Ip;
import net.mujiwulian.utils.JwtTokenUtil;
import net.mujiwulian.view.UserSimple;
import net.mujiwulian.vo.Result;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.Resource;

/**
 * @author silence
 * @Description: security配置
 */
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserSimpleService userSimpleService;
    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * @Description: security总体配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //禁用csrf模式
        http.csrf().disable().cors();
        // 表单登录  来身份认证
        http.formLogin().permitAll();

        //退出登录
        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout", "POST"))
                .logoutSuccessHandler((req, resp, auth) -> {
                    String authHeader = req.getHeader("Authorization");
                    jwtTokenUtil.removeToken(authHeader);
                    resp.setContentType("application/json;charset=UTF-8");
                    resp.getWriter().write(JSON.toJSONString(new Result<>(Result.CodeStatus.OK, "已退出登录")));
                })
                .clearAuthentication(true)
                .invalidateHttpSession(true);

        //放行静态资源
        http.authorizeRequests().antMatchers("/files/**", "/**/select**", "/disease/details").permitAll();
        //添加拦截验证
        http.authorizeRequests().anyRequest().authenticated();

        //不生成session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //自定义权限不足返回结果
        http.exceptionHandling().accessDeniedHandler((req, resp, auth) -> {
            log.warn("权限不足  访问路径: " + req.getRequestURI());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(new Result<>(Result.CodeStatus.ILLEGAL_TOKEN, "权限不足", null)));
        });
        //自定义未登录返回结果
        http.exceptionHandling().authenticationEntryPoint((req, resp, e) -> {
            log.warn("未登录  访问路径: " + req.getRequestURI());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(new Result<>(Result.CodeStatus.TOKEN_EXPIRED, "身份认证已过期", null)));
        });
        //自定义过滤器
        http.addFilterBefore(this.jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * @Description: 登录账号密码验证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * @Description: 密码加密bean
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return username -> {
            LambdaQueryWrapper<UserSimple> qw = Wrappers.lambdaQuery();
            UserSimple userSimple = userSimpleService.getOne(qw.eq(UserSimple::getUsername, username));
            if (userSimple != null) {
                return userSimple;
            } else {
                throw new UsernameNotFoundException("账号不存在");
            }
        };
    }

    /**
     * @Description: 修改默认登录form表单提交数据的接收方式(默认只能接受formData, 修改为可以接收json数据)
     */
    @Bean
    CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        //登陆成功
        filter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            UserSimple userSimple = (UserSimple) authentication.getPrincipal();
            userSimple.setPassword("PROTECTED");
            String token = jwtTokenUtil.generateToken(userSimple, Ip.getRealIp(req));
            resp.setContentType("application/json;charset=UTF-8");
            resp.setHeader("token", token);
            resp.getWriter().write(JSON.toJSONString(new Result<>(Result.CodeStatus.OK, "登陆成功")));
        });
        //登陆失败
        filter.setAuthenticationFailureHandler((req, resp, e) -> {
            String errorMessage = "Bad credentials";
            String msg = "账号或密码错误";
            if (!errorMessage.equals(e.getMessage())) {
                msg = "网络异常";
            }
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(new Result<>(Result.CodeStatus.FAIL, msg, null)));
        });
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }
}
